#!/bin/bash
#
# This file and its contents are supplied under the terms of the
# Common Development and Distribution License ("CDDL"), version 1.0.
# You may only use this file in accordance with the terms of version
# 1.0 of the CDDL.
#
# A full copy of the text of the CDDL should have accompanied this
# source.  A copy of the CDDL is also available via the Internet at
# http://www.illumos.org/license/CDDL.
#

#
# Copyright 2020 Joyent, Inc.
#

#
# Build the platform image (i.e., "boot_archive", "unix", etc) and the platform
# tar archive.  This program should be invoked via the "make live" target of
# the top-level Makefile in "smartos-live.git".
#

set -o pipefail
set -o nounset

if ! bi_wsroot=$(cd "$(dirname "$0")/.." && pwd) ||
    ! bi_arg0=$(basename "$0"); then
	printf 'failed to locate working directory\n' >&2
	exit 1
fi
bi_type='live'
bi_uid=

if ! . "$bi_wsroot/tools/lib/build_common.sh"; then
	printf 'failed to source "build_common.sh"\n' >&2
	exit 1
fi

bi_manifest=
bi_out_dir=
declare -a bi_in_dirs

#
# These variables are populated by "bi_find_buildstamp()":
#
bi_buildstamp=
bi_buildver=
bi_prefix=

bi_archive=
bi_kernel=

#
# The "bi_create_ramdisk" function stashes the lofi device path for the
# most recent ramdisk image in this variable:
#
bi_lofi_last=

bi_lofi_root=
bi_lofi_usr=

bi_file_root=
bi_file_usr=

bi_mnt_root=
bi_mnt_usr=

bi_root_size=

bi_root_pw=

bi_log=


function usage
{
	printf "Usage: %s [-p root_password] -m MANIFEST_FILE " "$bi_arg0"
	printf -- "-o OUTPUTDIR { INPUT_DIR ... }\n"
	exit 2
}

function fail_cleanup
{
	if [[ -n ${NO_CLEANUP:-} ]]; then
		printf 'WARNING: skipping cleaning; tmpdir = %s\n' \
		    "$bi_tmpdir"
		return
	fi

	#
	# Try to make sure we aren't sitting in a directory that we wish
	# to unmount or remove.
	#
	cd /tmp

	if [[ -n $bi_mnt_usr ]]; then
		pfexec umount -f "$bi_mnt_usr"
	fi
	if [[ -n $bi_mnt_root ]]; then
		pfexec umount -f "$bi_mnt_root"
	fi

	if [[ -n $bi_lofi_usr ]]; then
		pfexec lofiadm -d "$bi_lofi_usr"
	fi
	if [[ -n $bi_lofi_root ]]; then
		pfexec lofiadm -d "$bi_lofi_root"
	fi

	if [[ -n $bi_tmpdir && -d $bi_tmpdir ]]; then
		rm -rf "$bi_tmpdir"
	fi
}

function bi_find_buildstamp
{
	local bsfile="$bi_wsroot/proto/buildstamp"
	local bvfile="$bi_wsroot/configure-buildver"

	if [[ ! -f $bsfile ]]; then
		fail "build stamp file is missing: \"$bsfile\""
	fi

	bi_buildstamp=$(<"$bsfile")

	if [[ -z $bi_buildstamp ]]; then
		fail "build stamp file is empty: \"$bsfile\""
	fi

	bi_emit_info 'Build Stamp' "$bi_buildstamp"

	if [[ -f $bvfile ]]; then
		if ! bi_buildver=$(/usr/bin/head -n1 "$bvfile"); then
			fail "reading build version file \"$bvfile\""
		fi
	fi

	if [[ -n $bi_buildver ]]; then
		bi_emit_info 'Build Version' "$bi_buildver"
		bi_prefix="platform-$bi_buildver-$bi_buildstamp"
	else
		bi_prefix="platform-$bi_buildstamp"
	fi

}

function bi_run_tzcheck
{
	local tzcheck="$bi_wsroot/tools/tzcheck/tzcheck"

	bi_emit_start 'Checking timezone files...'

	if ! "$tzcheck" -f "$bi_manifest" -p "$bi_wsroot/proto"; then
		fail "not tzcheck clean"
	fi

	bi_emit_done
}

function bi_run_ucodecheck
{
	local ucodecheck="$bi_wsroot/tools/ucodecheck/ucodecheck"

	bi_emit_start 'Checking microcode files...'

	if ! "$ucodecheck" -f "$bi_manifest" -p "$bi_wsroot/proto"; then
		fail "not ucodecheck clean"
	fi

	bi_emit_done
}

#
# Create a blank ramdisk image file, then attach it as a lofi device.
#
function bi_create_ramdisk
{
	local crd_size=$1
	local crd_path=$2

	if [[ -z $crd_size || -z $crd_path ]]; then
		fail 'bi_create_ramdisk() requires both arguments'
	fi

	if [[ -f $crd_path ]]; then
		fail "ramdisk \"$crd_path\" already exists"
	fi

	bi_emit_start "Creating ramdisk file \"$crd_path\""

	if ! /usr/sbin/mkfile "${crd_size}k" "$crd_path"; then
		fail "failed to make ramdisk file \"$crd_path\""
	fi

	if ! crd_lofi=$(pfexec /usr/sbin/lofiadm -a "$crd_path"); then
		fail "failed to attach file \"$crd_path\" as lofi device"
	fi

	bi_lofi_last=$crd_lofi

	bi_emit_done
}

function bi_create_ufs
{
	local cuf_inode_density=$1
	local cuf_special=$2
	local cuf_path=$3
	local cuf_name=$4
	local cuf_out=

	if [[ -z $cuf_inode_density || -z $cuf_special ||
	    -z $cuf_path ]]; then
		fail 'bi_create_ufs() requires three arguments'
	fi

	if [[ ! -b $cuf_special ]]; then
		fail "\"$cuf_special\" is not a block device"
	fi

	bi_emit_start "Creating UFS file system ($cuf_name)"

	if ! cuf_out=$(pfexec /usr/sbin/newfs -o space -m 0 \
	    -i "$cuf_inode_density" -b 4096 "$cuf_special" \
	    </dev/null 2>&1); then
		bi_emit_newline
		fail "newfs failure for \"$cuf_special\": $cuf_out"
	fi

	if ! mkdir -p "$cuf_path"; then
		fail "failed to mkdir \"$cuf_path\" for mount"
	fi

	if ! pfexec /usr/sbin/mount -F ufs -o nologging "$cuf_special" \
	    "$cuf_path"; then
		fail "failed to mount \"$cuf_special\" on \"$cuf_path\""
	fi

	bi_emit_done
}

function bi_copy_files
{
	local builder="$bi_wsroot/tools/builder/builder"
	local builder_log="$bi_tmpdir/builder.output"

	bi_emit_start 'Copying files from proto areas to image'

	#
	# A torrent of mostly uninteresting output pours forth from the
	# "builder" program.  We'll keep it in a file just in case it's needed
	# later.
	#
	if ! pfexec "$builder" "$bi_manifest" "$bi_mnt_root" \
	    "${bi_in_dirs[@]}" 2>&1 | bi_log_tee >"$builder_log"; then
		#bi_emit_newline
		#printf 'Builder failures:\n' >&2
		#/usr/bin/grep 'FAILED$' "$builder_log" >&2
		bi_extra=$(/usr/bin/grep 'FAILED$' "$builder_log")
		fail "\"builder\" failed to copy from proto area to image"
	fi

	bi_emit_done
}

function bi_gen_whatis
{
	local tools_proto="$bi_wsroot/projects/illumos/usr/src/tools/proto"
	local tools_man="$tools_proto/root_i386-nd/opt/onbld/bin/i386/man"

	bi_emit_start 'Generating whatis database'

	if [[ ! -e "$tools_man" ]]; then
		fail "could not find the illumos tools man at \"$tools_man\""
	fi

	if ! bi_extra=$(pfexec "$tools_man" -M \
	    "$bi_mnt_root/usr/share/man:$bi_mnt_root/smartdc/man" -w \
	    2>&1); then
		fail "could not generate whatis database"
	fi

	#
	# "man -w" will, at times, output diagnostic messages that are not
	# fatal.
	#
	if [[ -n $bi_extra ]]; then
		bi_emit_newline
		printf "man -w output:\n"
		sed -e 's/^/| /' <<< "$bi_extra"
	fi

	bi_emit_done
}

function bi_smf_import
{
	local smf_importer="$bi_wsroot/tools/smf_import"

	bi_emit_start 'Importing GZ SMF manifests'

	if ! bi_extra=$(pfexec "$smf_importer" "$bi_mnt_root" 2>&1); then
		fail "failed to import GZ SMF manifests"
	fi

	bi_emit_done
}

function bi_smf_seeds
{
	local smf_seeds="$bi_wsroot/tools/build_seeds"

	bi_emit_start 'Building SMF seeds'

	if ! bi_extra=$(pfexec "$smf_seeds" "$bi_mnt_root" 2>&1); then
		fail "failed to build SMF seeds"
	fi

	bi_emit_done
}

function bi_create_password
{
	local pwgen="$bi_wsroot/tools/pwgen"
	local cryptpass="$bi_wsroot/tools/cryptpass"
	local cp_password
	local cp_hash
	local shadow="$bi_mnt_root/etc/shadow"
	local stash="$bi_out_dir/$bi_prefix/root.password"

	if [[ -z "$bi_root_pw" ]]; then
		bi_emit_start 'Setting random "root" password'

		#
		# Generate a random root password.
		#
		if ! cp_password=$("$pwgen" -B -c -n 16 1) ||
		    [[ -z $cp_password ]]; then
			fail 'unable to generate root password.'
		fi
	else
		cp_password="$bi_root_pw"
	fi

	bi_emit_info 'Password' "$cp_password"

	#
	# Hash the password for use in /etc/shadow.
	#
	if ! cp_hash=$("$cryptpass" "$cp_password") ||
	    [[ -z $cp_hash ]]; then
		fail 'unable to generate root password hash.'
	fi

	#
	# Replace the existing "root" account row in the ramdisk /etc/shadow
	# file.  Note that we use "pfexec tee" when creating the new file, as
	# the build user may not have permission to write in the mounted image
	# using regular shell redirection.
	#
	if ! pfexec /usr/bin/nawk -v "cp_hash=$cp_hash" '
	    BEGIN { FS=":"; OFS=":"; }
	    $1 == "root" { $2 = cp_hash; } { print; }' "$shadow" \
	    | pfexec /usr/bin/tee "$shadow.new" >/dev/null; then
		pfexec /usr/bin/rm -f "$shadow.new"
		fail 'unable to update ramdisk /etc/shadow'
	fi
	if ! pfexec /usr/bin/mv "$shadow.new" "$shadow" ||
	    ! pfexec /usr/bin/chmod 400 "$shadow"; then
		fail 'unable to update ramdisk /etc/shadow'
	fi

	#
	# Stash the generated root password as a plain text build artefact
	# so that we can find it later if we need it.
	#
	if ! printf '%s\n' "$cp_password" >"$stash"; then
		fail "unable to stash root password in \"$stash\""
	fi

	bi_emit_done
}

#
# Generate the gitstatus file and make sure it's included in the platform image
# as /etc/versions/build. We already did this via nightly(1), but we re-do it
# here in case we updated other components without re-building illumos.
#
# On boot, the kernel will pull this in.
#
# We also generate the platform's /etc/release here.
#
function bi_gen_version_files
{
	local build_etcrelease="$bi_wsroot/tools/build_etcrelease"
	local bvfile="$bi_mnt_root/etc/versions/build"
	local gitstatus="$bi_out_dir/gitstatus.json"

	bi_emit_start 'Building version files'

	"$build_etcrelease" -g >"$gitstatus" ||
	    fail "could not generate $gitstatus"

	pfexec cp "$gitstatus" "$bi_archive.gitstatus" ||
	    fail "failed to copy gitstatus"

	if ! cmp "$gitstatus" "$bvfile" >/dev/null 2>&1; then
		pfexec cp "$gitstatus" "$bvfile" ||
		    fail "could not copy build version"
	fi

	#
	# Create this file using "pfexec tee", as the build user might not have
	# permission to write in the mounted image using regular shell
	# redirection.
	#
	"$build_etcrelease" -v "$bi_buildstamp" |
	    pfexec /usr/bin/tee "$bi_mnt_root/etc/release" >/dev/null ||
	     fail 'failed to build "/etc/release" file'

	bi_emit_done
}

function bi_ship_manifest
{
	local fl_manifest="$bi_archive.manifest"
	local fl_manifest_img="$bi_mnt_usr/share/smartos/manifest"

	bi_emit_start 'Generating shipped file manifest'

	#
	# Generate a manifest including the MD5 hash of every file we ship.
	# This is included in the output as a separate build artefact.
	#
	if ! (cd "$bi_mnt_root" && pfexec find . -type f | sort |
	    pfexec xargs md5sum) >"$fl_manifest"; then
		fail "could not generate manifest file"
	fi

	#
	# We include the manifest in the image as well.  Make sure it has
	# the correct ownership.
	#
	if ! pfexec /usr/bin/mkdir "$(dirname "$fl_manifest_img")" ||
	    ! pfexec /usr/bin/cp "$fl_manifest" "$fl_manifest_img" ||
	    ! pfexec /usr/bin/chown 0:0 "$fl_manifest_img"; then
		fail "could not copy manifest file into image"
	fi

	bi_emit_done
}

function bi_file_fixups
{
	local kernel_src="$bi_mnt_root/platform/i86pc/kernel/amd64/unix"

	bi_emit_start 'Fixing shipped files'

	if ! /usr/bin/mkdir -p "$(dirname "$bi_kernel")" ||
	    ! /usr/bin/cp "$kernel_src" "$bi_kernel"; then
		fail "could not copy the kernel"
	fi

	#
	# Need to ensure that several files in the image are empty
	#
	for f in var/log/syslog var/adm/wtmpx; do
		if ! pfexec /usr/bin/rm -f "$bi_mnt_root/$f" ||
		    ! pfexec /usr/bin/touch "$bi_mnt_root/$f" ||
		    ! pfexec /usr/bin/chown 0:0 "$bi_mnt_root/$f"; then
			fail "could not create empty file \"$f\""
		fi
	done

	if ! pfexec /usr/bin/chown 0:0 "$bi_mnt_root/"; then
		fail "could not fix permissions on /"
	fi

	#
	# The system needs certain files in early boot before /usr is mounted.
	# We create a tar archive of these files, then unmount /usr and
	# extract it into the /usr directory underneath.
	#
	local -a underfiles=(
		"bin/["
		"bin/cat"
		"bin/head"
		"bin/i86/ksh93"
		"bin/ls"
		"kernel/sched/amd64/FSS"
		"lib/fm/libfmevent.so"
		"lib/fm/libfmevent.so.1"
		"lib/fm/libtopo.so"
		"lib/fm/libtopo.so.1"
		"lib/libast.so"
		"lib/libast.so.1"
		"lib/libcmd.so"
		"lib/libcmd.so.1"
		"lib/libdll.so"
		"lib/libdll.so.1"
		"lib/libexacct.so"
		"lib/libexacct.so.1"
		"lib/libfstyp.so"
		"lib/libfstyp.so.1"
		"lib/libidmap.so"
		"lib/libidmap.so.1"
		"lib/libipmi.so"
		"lib/libipmi.so.1"
		"lib/libpkcs11.so"
		"lib/libpkcs11.so.1"
		"lib/libpool.so"
		"lib/libpool.so.1"
		"lib/libproject.so"
		"lib/libproject.so.1"
		"lib/libshell.so"
		"lib/libshell.so.1"
		"lib/libsmbios.so"
		"lib/libsmbios.so.1"
		"lib/libsum.so"
		"lib/libsum.so.1"
		"sbin/lofiadm"
	)
	local fl_tarfile="$bi_tmpdir/usr_underfiles.tar"

	if ! (cd "$bi_mnt_usr" && /usr/bin/tar cbf 512 "$fl_tarfile" \
	    "${underfiles[@]}"); then
		fail "could not copy files to put under /usr"
	fi

	#
	# Now, unmount /usr
	#
	if ! pfexec /sbin/umount "$bi_mnt_usr"; then
		fail "could not unmount /usr image"
	fi
	bi_mnt_usr=
	if ! pfexec /usr/sbin/lofiadm -d "$bi_lofi_usr"; then
		fail "could not detach /usr lofi device"
	fi
	bi_lofi_usr=

	#
	# Put the under files in place in the root image
	#
	if ! (cd "$bi_mnt_root/usr" && pfexec /usr/bin/tar xbf 512 \
	    "$fl_tarfile"); then
		fail "could not copy files under /usr in root image"
	fi

	#
	# In our image, ksh93 is a hardlink to isaexec.  Rather than replicate
	# that arrangement in the root file system, we just symlink to the
	# 32-bit binary directly.
	#
	if ! pfexec /usr/bin/ln -s 'i86/ksh93' "$bi_mnt_root/usr/bin/sh"; then
		fail "failed to create ksh93 symlink"
	fi

	bi_emit_done
}

function bi_compress_usr
{
	local usrlgz="$bi_mnt_root/usr.lgz"

	bi_emit_start 'Compressing /usr image to include in root image'

	#
	# The /usr image is included as a compressed file in the root image.
	#
	if ! pfexec /usr/sbin/lofiadm -C "$bi_file_usr"; then
		fail "could not compress /usr image"
	fi

	if ! pfexec /usr/bin/mv "$bi_file_usr" "$usrlgz" ||
	    ! pfexec /usr/bin/chown 0:0 "$usrlgz"; then
		fail "could not copy /usr image into root image"
	fi

	bi_emit_done
}

function bi_finish_archive
{
	bi_emit_start 'Finishing boot_archive creation'

	if ! pfexec /sbin/umount "$bi_mnt_root"; then
		fail "could not unmount root image"
	fi
	bi_mnt_root=

	if ! pfexec /usr/sbin/lofiadm -d "$bi_lofi_root"; then
		fail "could not detach root lofi device"
	fi
	bi_lofi_root=

	#
	# Move the completed boot_archive into the output directory.
	#
	if ! /usr/bin/mv "$bi_file_root" "$bi_archive"; then
		fail "could not move boot_archive into output directory"
	fi

	#
	# The boot_archive file was created with mkfile(1), so it probably
	# has inappropriate permissions.
	#
	if ! /usr/bin/chmod 644 "$bi_archive"; then
		fail "could not set boot_archive permissions"
	fi

	#
	# Include the boot_archive hash in the build output.
	#
	if ! /usr/bin/digest -a sha1 "$bi_archive" > "$bi_archive.hash"; then
		fail "could not store hash of boot_archive in output directory"
	fi

	version_dir="$bi_out_dir/$bi_prefix/etc/version"
	if ! /usr/bin/mkdir -p "$version_dir"; then
		fail "could not create \"$version_dir\""
	fi

	if ! printf '%s\n' "$bi_buildstamp" >"$version_dir/platform"; then
		fail "could not store buildstamp in platform tarball"
	fi

	if ! printf '%s\n' "$bi_buildstamp" >"$bi_out_dir/buildstamp"; then
		fail "could not store buildstamp in output directory"
	fi

	bi_emit_done
}

function bi_platform_tar
{
	bi_emit_start 'Creating platform tar archive'

	if ! (cd "$bi_out_dir" && gtar -zcf "$bi_prefix.tgz" \
	    "$bi_prefix"); then
		fail "could not create platform tar archive"
	fi

	bi_emit_done
}

function bi_safe_update_symlink
{
	local source_file=$1
	local target=$2

	if [[ -z $source_file || -z $target ]]; then
		fail 'bi_safe_update_symlink args: <source_file> <target>'
	fi

	if ! (cd "$(dirname "$target")" && test -e "$source_file"); then
		fail "symlink target \"$source_file\" does not appear to exist"
	fi

	if [[ ! -L $target ]]; then
		if [[ -e $target ]]; then
			fail "\"$target\" exists, but is not a symlink"
		fi
	fi

	#
	# We use pfexec to remove the old symlink, in case there are latent
	# file system permission issues from when this program used to run as
	# root.
	#
	if ! pfexec /usr/bin/rm -f "$target" ||
	    ! /usr/bin/ln -s "$source_file" "$target"; then
		fail "failed to create symlink \"$target\" -> \"$source_file\""
	fi

	bi_emit_info 'Linked build output' \
	    "$(basename "$target") -> $source_file"
}


#
# Process options:
#
while getopts "m:o:p:" c $@; do
	case "$c" in
	m)
		bi_manifest=$OPTARG
		;;
	o)
		bi_out_dir=$OPTARG
		;;
	p)
		bi_root_pw="$OPTARG"
		;;
	*)
		usage
		;;
	esac
done

trap 'bi_early_exit' EXIT
trap 'bi_interrupt' SIGINT SIGHUP

if ! bi_uid=$(/usr/bin/id -u); then
	fail 'could not determine user id'
fi

#
# Gather the remaining positional arguments to form the list of input
# directories.
#
shift "$(( OPTIND - 1 ))"

while (( $# > 0 )); do
	bi_in_dirs+=( "$1" )
	shift
done

#
# Check to make sure we have all of the arguments that we need:
#
if [[ -z $bi_manifest || -z $bi_out_dir ]]; then
	fail '-m and -o are required'
fi

if (( ${#bi_in_dirs[@]} < 1 )); then
	fail 'at least one input directory is required'
fi

bi_big_banner 'Building platform image'

#
# Get us to the root of the workspace:
#
if ! cd "$bi_wsroot"; then
	fail "could not chdir to workspace root \"$bi_wsroot\""
fi

#
# Ensure that the output directory exists, and belongs to the correct
# user:
#
if [[ -d $bi_out_dir ]]; then
	if ! pfexec /usr/bin/chown -R "$bi_uid" "$bi_out_dir"; then
		fail "could not fix ownership on output directory"
	fi
else
	if ! /usr/bin/mkdir -p "$bi_out_dir"; then
		fail "could not create output directory \"$bi_out_dir\""
	fi
fi

#
# Load the build stamp (and potentially a build version), and emit some
# information about what we're going to do.
#
bi_find_buildstamp
bi_emit_info 'Manifest File' "$bi_manifest"
bi_emit_info 'Output Directory' "$bi_out_dir"
for (( i = 0; i < ${#bi_in_dirs[@]}; i++ )); do
	bi_emit_info "Input Directory[$i]" "${bi_in_dirs[$i]}"
done
printf '\n'

#
# Set up trace logging into a log file:
#
if [[ -d "$bi_wsroot/log" ]]; then
	if ! pfexec /usr/bin/chown -R "$bi_uid" "$bi_wsroot/log"; then
		fail "could not fix ownership on log directory"
	fi
else
	if ! /usr/bin/mkdir -p "$bi_wsroot/log"; then
		fail "could not create log directory"
	fi
fi
bi_log_setup "$bi_wsroot/log/build_live.$bi_buildstamp.$(date +%s).log"

#
# Run pre-flight checks:
#
bi_run_tzcheck
bi_run_ucodecheck

bi_setup_work_dir

#
# Create and mount the "/" (root) ramdisk image:
#
bi_file_root="$bi_tmpdir/root.image"
bi_root_size=300000
if strings "$bi_wsroot/proto/kernel/amd64/genunix" |
    grep "DEBUG enabled" >/dev/null; then
	#
	# Increase root size by 3M if we have a DEBUG kernel.
	#
	bi_root_size=$(( bi_root_size + 3000))
fi
bi_create_ramdisk "$bi_root_size" "$bi_file_root"
bi_lofi_root="$bi_lofi_last"
bi_mnt_root="$bi_tmpdir/a"
bi_create_ufs 12248 "$bi_lofi_root" "$bi_mnt_root" "/"

#
# Create the /usr directory in the ramdisk:
#
bi_mnt_usr="$bi_mnt_root/usr"
if ! pfexec mkdir "$bi_mnt_usr"; then
	fail "could not mkdir \"$bi_mnt_usr\""
fi

#
# Create and mount the "/usr" ramdisk image:
#
bi_file_usr="$bi_tmpdir/usr.image"
bi_create_ramdisk 448000 "$bi_file_usr"
bi_lofi_usr="$bi_lofi_last"
bi_create_ufs 14000 "$bi_lofi_usr" "$bi_mnt_usr" "/usr"

#
# Set up the output locations where build artefacts will be stored:
#
bi_archive="$bi_out_dir/$bi_prefix/i86pc/amd64/boot_archive"
bi_kernel="$bi_out_dir/$bi_prefix/i86pc/kernel/amd64/unix"

if ! mkdir -p "$(dirname "$bi_archive")" "$(dirname "$bi_kernel")"; then
	fail "could not create build output directories"
fi

#
# Assemble the boot_archive:
#
bi_copy_files
bi_gen_whatis
bi_smf_import
bi_smf_seeds
bi_create_password
bi_gen_version_files
bi_ship_manifest
bi_file_fixups
bi_compress_usr
bi_finish_archive
bi_platform_tar

#
# Maintain "latest" symlinks to whichever live image we just assembled.
#
bi_safe_update_symlink "$bi_prefix" "$bi_out_dir/platform-latest"
bi_safe_update_symlink "$bi_prefix.tgz" "$bi_out_dir/platform-latest.tgz"

bi_cleanup_work_dir

bi_exit 0
